package it.traveldream.EJB;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Iterator;
import java.util.List;

import it.traveldream.EJB.DTO.AlloggioDTO;
import it.traveldream.EJB.DTO.DisponibilitaDTO;
import it.traveldream.EJB.DTO.EscursioneDTO;
import it.traveldream.EJB.DTO.TrasportoSingoloDTO;
import it.traveldream.EJB.entities.Alloggi;
import it.traveldream.EJB.entities.Disponibilita;
import it.traveldream.EJB.entities.Escursioni;
import it.traveldream.EJB.entities.TrasportiSingoli;
import it.traveldream.utility.DTO2Entities;
import it.traveldream.utility.Entities2DTO;

import javax.annotation.Resource;
import javax.annotation.security.RolesAllowed;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceUnit;
import javax.transaction.UserTransaction;

/**
 * Session Bean implementation class SBEJBBeanImpl
 */
@Stateless
public class SBEJBBeanImpl implements SBEJBBean {

	@PersistenceContext
	EntityManager em;

	public SBEJBBeanImpl() {
		// TODO Auto-generated constructor stub
	}

	public SBEJBBeanImpl(EntityManager emg) {
		if(em==null){
			em=emg;
		}
	}

	/**
	 * Metodo per recuperare tutti i {@link TrasportiSingoli}
	 * 
	 * @return tutti i risultati {@link TrasportoSingoloDTO} inseriti in un'{@link ArrayList}
	 */
	@Override
	public ArrayList<TrasportoSingoloDTO> getTrasporti(){

		ArrayList<TrasportoSingoloDTO> result = new ArrayList<TrasportoSingoloDTO>();
		Iterator<TrasportiSingoli> i;
		List<TrasportiSingoli> ret;

		ret=em.createQuery("SELECT t FROM TrasportiSingoli t WHERE t.deleted = 0").getResultList();

		i=ret.iterator();

		while(i.hasNext()){
			result.add(Entities2DTO.convertTrasportiSingoli(i.next()));
		}

		return result;
	}

	/**
	 * Metodo per recuperare tutti le {@link Escursioni}
	 * 
	 * @return tutti i risultati {@link EscursioneDTO} inseriti in un'{@link ArrayList}
	 */
	@Override
	public ArrayList<EscursioneDTO> getEscursioni(){

		ArrayList<EscursioneDTO> ret = new ArrayList<EscursioneDTO>();
		List<Escursioni> entities;
		Iterator<Escursioni> iterator;

		entities = em.createQuery("select e from Escursioni e WHERE e.deleted = 0").getResultList();

		iterator = entities.iterator();

		while(iterator.hasNext()){
			ret.add(Entities2DTO.convertEscursioni(iterator.next()));
		}

		return ret;

	}

	/**
	 * Metodo per recuperare tutti le {@link Alloggi}
	 * 
	 * @return tutti i risultati {@link AlloggioDTO} inseriti in un'{@link ArrayList}
	 */
	@Override
	public ArrayList<AlloggioDTO> getAllAlloggi(){
		ArrayList<AlloggioDTO> result = new ArrayList<AlloggioDTO>();
		List<Alloggi> entities;
		Iterator<Alloggi> iterator;

		entities = em.createQuery("select a from Alloggi a WHERE a.deleted = 0").getResultList();

		iterator = entities.iterator();

		while(iterator.hasNext()){
			result.add(Entities2DTO.convertAlloggi(iterator.next()));
		}

		return result;
	}

	/**
	 * Aggiunge un entit� {@link TrasportiSingoli} al database
	 * 
	 * @param t = Il DTO da cui derivare l'entit�
	 * 
	 */
	@Override
	@RolesAllowed({"ADMINS"})
	public void addTrasporto(TrasportoSingoloDTO t) throws Exception {

		TrasportiSingoli newt = new TrasportiSingoli();

		newt=DTO2Entities.convertTrasportoSingoloDTO(t);

		try{
			em.persist(newt);
		} catch(Exception e){
			throw new Exception("ERRORE INSERIMENTO TRASPORTO: " + e.getMessage());
		}

	}

	/**
	 * Aggiorna i dati di un {@link TrasportiSingoli} sul DB
	 * 
	 * @param ts = il {@link TrasportoSingoloDTO} contenente i dati aggiornati
	 * 
	 * @return il {@link TrasportoSingoloDTO} che deve coincidere con il parametro passato
	 * 
	 */
	@Override
	@RolesAllowed({"ADMINS"})
	public TrasportoSingoloDTO updateTrasporto(TrasportoSingoloDTO ts) throws Exception{
		
		if(!isTrasportoNeverUsed(ts)){
			throw new Exception("ATTENZIONE! TRASPORTO PRESENTE IN UN PACCHETTO, IMPOSSIBILE MODIFICARLO: ZIO PORCO");
		}

		TrasportiSingoli updated = new TrasportiSingoli();

		updated=DTO2Entities.convertTrasportoSingoloDTO(ts);

		try{
			updated=em.merge(updated);
			em.flush();
		}catch(Exception e){
			throw new Exception("ERRORE AGGIORNAMENTO TRASPORTO: "+e.getMessage());
		}

		ts=Entities2DTO.convertTrasportiSingoli(updated); //if(ts.equals(metodo(updated)))

		return ts;

	}

	/**
	 * Aggiorna i dati di un {@link TrasportiSingoli} sul DB, settando "deleted" a 1, cancellando lato applicazione il dato
	 * 
	 * @param ts = il {@link TrasportoSingoloDTO} contenente i dati aggiornati
	 * 
	 */
	@Override
	@RolesAllowed({"ADMINS"})
	public void deleteTrasporto(TrasportoSingoloDTO t) throws Exception{
		
		if(!isTrasportoNeverUsed(t)){
			throw new Exception("ATTENZIONE! TRASPORTO PRESENTE IN UN PACCHETTO, IMPOSSIBILE CANCELLARLO: CANE");
		}

		TrasportiSingoli toDelete=new TrasportiSingoli();

		// il campo � messo a 1 in modo da settare il flag di 'cancellato'
		t.setDeleted(1);

		toDelete = DTO2Entities.convertTrasportoSingoloDTO(t);

		try{
			em.merge(toDelete);
			em.flush();
			em.detach(toDelete);
		} catch(Exception e){
			throw new Exception("ERRORE CANCELLAZIONE TRASPORTO: " + e.getMessage());
		}

	}

	/**
	 * Aggiunge un entit� {@link Escursioni} al database
	 * 
	 * @param t = Il DTO da cui derivare l'entit�
	 * 
	 */
	@Override
	@RolesAllowed({"ADMINS"})
	public void addEscursione(EscursioneDTO es) throws Exception {

		Escursioni newe = new Escursioni();

		newe=DTO2Entities.convertEscursioneDTO(es);

		try{
			em.persist(newe);
		} catch(Exception e){
			throw new Exception("ERRORE INSERIMENTO TRASPORTO: " + e.getMessage());
		}

	}

	/**
	 * Aggiorna i dati di un {@link Escursioni} sul DB
	 * 
	 * @param ts = il {@link EscursioneDTO} contenente i dati aggiornati
	 * 
	 * @return il {@link EscursioneDTO} che deve coincidere con il parametro passato
	 * 
	 */
	@Override
	@RolesAllowed({"ADMINS"})
	public EscursioneDTO updateEscursione(EscursioneDTO ts) throws Exception{
		
		if(!isEscursioneNeverUsed(ts)){
			throw new Exception("ATTENZIONE! ESCURSIONE PRESENTE IN UN PACCHETTO, IMPOSSIBILE MODIFICARLA: BUBU");
		}

		Escursioni updated = new Escursioni();

		updated=DTO2Entities.convertEscursioneDTO(ts);

		try{
			updated=em.merge(updated);
			em.flush();
		}catch(Exception e){
			throw new Exception("ERRORE AGGIORNAMENTO TRASPORTO: "+e.getMessage());
		}

		ts=Entities2DTO.convertEscursioni(updated); //if(ts.equals(metodo(updated)))

		return ts;

	}

	/**
	 * Aggiorna i dati di un {@link Escursioni} sul DB, settando "deleted" a 1, cancellando lato applicazione il dato
	 * 
	 * @param ts = il {@link EscursioneDTO} contenente i dati aggiornati
	 * 
	 */
	@Override
	@RolesAllowed({"ADMINS"})
	public void deleteEscursione(EscursioneDTO t) throws Exception{
		
		if(!isEscursioneNeverUsed(t)){
			throw new Exception("ATTENZIONE! ESCURSIONE PRESENTE IN UN PACCHETTO, IMPOSSIBILE CANCELLARLA: CACACA");
		}

		Escursioni toDelete=new Escursioni();

		// il campo � messo a 1 in modo da settare il flag di 'cancellato'
		t.setDeleted(1);

		toDelete = DTO2Entities.convertEscursioneDTO(t);

		try{
			em.merge(toDelete);
			em.flush();
			em.detach(toDelete);
		} catch(Exception e){
			throw new Exception("ERRORE CANCELLAZIONE TRASPORTO: " + e.getMessage());
		}

	}
	
	/**
	 * Aggiorna i dati di un {@link Alloggi} sul DB
	 * 
	 * @param ts = il {@link AlloggioDTO} contenente i dati aggiornati
	 * 
	 * @return il {@link AlloggioDTO} che deve coincidere con il parametro passato
	 * 
	 */
	@Override
	@RolesAllowed({"ADMINS"})
	public AlloggioDTO updateAlloggio(AlloggioDTO a) throws Exception{
		
		if(!isAlloggioNeverUsed(a)){
			throw new Exception("ATTENZIONE! ALLOGGIO PRESENTE IN UN PACCHETTO, IMPOSSIBILE MODIFICARLO: AHIAHIAHI");
		}

		Alloggi updated = new Alloggi();

		updated=DTO2Entities.convertAllogioDTO(a);

		try{
			updated=em.merge(updated);
			em.flush();
		}catch(Exception e){
			throw new Exception("ERRORE AGGIORNAMENTO TRASPORTO: "+e.getMessage());
		}

		a=Entities2DTO.convertAlloggi(updated); //if(ts.equals(metodo(updated)))

		return a;

	}
	
	/**
	 * Aggiorna i dati di un {@link Alloggi} sul DB, settando "deleted" a 1, cancellando lato applicazione il dato
	 * 
	 * @param ts = il {@link AlloggioDTO} contenente i dati aggiornati
	 * 
	 */
	@Override
	@RolesAllowed({"ADMINS"})
	public void deleteAlloggio(AlloggioDTO t) throws Exception{
		
		if(!isAlloggioNeverUsed(t)){
			throw new Exception("ATTENZIONE! ALLOGGIO PRESENTE IN UN PACCHETTO, IMPOSSIBILE CANCELLARLA: CACACA");
		}

		Alloggi toDelete=new Alloggi();

		// il campo � messo a 1 in modo da settare il flag di 'cancellato'
		t.setDeleted(1);

		toDelete = DTO2Entities.convertAllogioDTO(t);

		try{
			em.merge(toDelete);
			em.flush();
			em.detach(toDelete);
		} catch(Exception e){
			throw new Exception("ERRORE CANCELLAZIONE TRASPORTO: " + e.getMessage());
		}
		
		try{
			em.createNativeQuery("DELETE FROM DISPONIBILITA WHERE id_alloggio="+toDelete.getIdAlloggio()).executeUpdate();
		}catch(Exception e){
			throw new Exception("ERRORE CANCELLAZIONE DISPONIBILITA DOPO CANCELLAZIONE ALLOGGIO: "+e.getMessage());
		}

	}

	/**
	 * Inserisce le relative disponibilita giorno per giorno di un {@link Alloggi}, per farlo chiama insertDisponibilita
	 * 
	 * @param vari = tutti i parametri necessari per effettuare i persist delle entita {@link Disponibilita}
	 */
	@Override
	@RolesAllowed({"ADMINS"})
	public void insertAlloggio(AlloggioDTO newa, Date dataInizioDisp, Date dataFineDisp, String tipocamera, int numerocamere, double prezzo) throws Exception{


		GregorianCalendar gcal = new GregorianCalendar();
		GregorianCalendar gcal2 = new GregorianCalendar();

		// IL GREGORIAN CALENDAR AIUTA A CICLARE DATA PER DATA
		gcal.setTime(dataInizioDisp);
		gcal2.setTime(dataFineDisp);
		gcal2.add(Calendar.DAY_OF_YEAR, 1);

		// ESCO DAL CICLO QUANDO LA DATA RAGGIUNTA E' MINORE STRETTAMENTE DI "DATA FINE DISPONIBILITA + 1"
		while (gcal.getTime().before(gcal2.getTime())) {
			insertDisponibilita(newa, gcal.getTime(), tipocamera, numerocamere, prezzo);
			gcal.add(Calendar.DAY_OF_YEAR, 1);
		}

	}

	/**
	 * Inserisce un nuovo {@link Alloggi} nel DB
	 * 
	 * @param newa = il {@link AlloggioDTO} per effettuare il persist dell'entita {@link Alloggi}
	 */
	@Override
	@RolesAllowed({"ADMINS"})
	public void insertAlloggio(AlloggioDTO newa) throws Exception{

		Alloggi entity = DTO2Entities.convertAllogioDTO(newa);
		try{
			em.persist(entity);
		}catch(Exception e){
			throw new Exception("ERRORE INSERIMENTO ALLOGGIO: "+e.getMessage());
		}

	}

	/**
	 * Inserisce ogni disponibilita nel DB, chiamato sempre e soltanto da insertAlloggio
	 * 
	 * @param newa
	 * @param dataDisp
	 * @param tipocamera
	 * @param numerocamere
	 * @param prezzo
	 * @throws Exception in caso di eccezione lanciata dall'em.persist(entity)
	 */
	public void insertDisponibilita(AlloggioDTO newa, Date dataDisp, String tipocamera, int numerocamere, double prezzo) throws Exception{
		DisponibilitaDTO ddto = new DisponibilitaDTO();
		ddto.setAlloggio(newa);
		ddto.setData(dataDisp);
		ddto.setNumerocamere(numerocamere);
		ddto.setPrezzo(prezzo);
		ddto.setTipoCamera(tipocamera);

		if(!isAlreadyDisponibilita(newa, dataDisp, tipocamera)){

			Disponibilita entity = new Disponibilita();
			entity = DTO2Entities.convertDisponibilitaDTO(ddto);

			try{
				em.persist(entity);
			}catch(Exception e){
				throw new Exception("ERRORE INSERIMENTO DISPONIBILITA DEL "+ddto.getData().toString()+": "+e.getMessage());
			}

		}else{
			throw new Exception("ERRORE, LA DISPONIBILITA DEL "+ddto.getData().toString()+" E' GIA' PRESENTE NEL DB");
		}
	}

	/**
	 * Chiamato da {@link PacketBeanImpl}.insertNewPacket, quando inserisco un pacchetto assegno delle disponibilita, perci� devo tenerne traccia
	 * 
	 * @param temp = l'entit� {@link Disponibilita} di cui fare l'update
	 * @throws Exception quando le tuple modificate sul DB sono diverse da 1
	 */
	public void updateDisponibilita(Disponibilita temp) throws Exception{
		try{
			int rows = em.createQuery("UPDATE Disponibilita d SET d.numerocamere = "+ temp.getNumerocamere() + " WHERE d.idDisponibilita = " + temp.getIdDisponibilita()).executeUpdate();
			if(rows!=1){
				throw new Exception("disponibilita modificate "+rows+" anzich� 1 ZIO PORCONE");
			}
		}catch(Exception e){
			throw new Exception(e.getMessage());
		}
	}

	/**
	 * Metodo di controllo se una disponibilita esiste nel DB prima di inserirla
	 * 
	 * @param i parametri passati al metodo chiamante
	 * 
	 * @return {@link true} se esiste, {@link false} altrimenti
	 */
	public boolean isAlreadyDisponibilita(AlloggioDTO newa, Date dataDisp, String tipocamera){
		Alloggi allo = DTO2Entities.convertAllogioDTO(newa);
		if(em.createQuery("SELECT d FROM Disponibilita d WHERE d.alloggi= :all AND d.tipoCamera= '"+ tipocamera +"' AND d.data= :data").setParameter("all", allo).setParameter("data", dataDisp).getResultList().isEmpty()){
			return false;	
		}
		return true;
	}
	
	/**
	 * Controllo se un alloggio, prima di modificarlo o cancellarlo, � usato in qualche pacchetto
	 * 
	 * @param a = l'{@link AlloggioDTO}
	 * @return {@link true} se non � mai utilizzato, {@link false} altrimenti
	 */
	public boolean isAlloggioNeverUsed(AlloggioDTO a){
		Alloggi all = DTO2Entities.convertAllogioDTO(a);
		if(em.createQuery("SELECT p FROM Pacchetti p WHERE p.alloggi= :all").setParameter("all", all).getResultList().isEmpty()){
			return true;
		}
		return false;
	}
	
	/**
	 * Controllo se un'escursione, prima di modificarlo o cancellarlo, � usato in qualche pacchetto
	 * 
	 * @param a = l'{@link EscursioneDTO}
	 * @return {@link true} se non � mai utilizzato, {@link false} altrimenti
	 */
	public boolean isEscursioneNeverUsed(EscursioneDTO e){
		
		if(em.createNativeQuery("SELECT * FROM Escursioni_x_Pacchetti WHERE id_escursione="+e.getIdEscursione()).getResultList().isEmpty()){
			return true;
		}
		return false;
	}
	
	/**
	 * Controllo se un trasporto, prima di modificarlo o cancellarlo, � usato in qualche pacchetto
	 * 
	 * @param a = il {@link TrasportoDTO}
	 * @return {@link true} se non � mai utilizzato, {@link false} altrimenti
	 */
	public boolean isTrasportoNeverUsed(TrasportoSingoloDTO t){
		TrasportiSingoli trasp = DTO2Entities.convertTrasportoSingoloDTO(t);
		if(em.createQuery("SELECT p FROM Pacchetti p WHERE p.trasportiSingoli1= :trasp").setParameter("trasp", trasp).getResultList().isEmpty()){
			if(em.createQuery("SELECT p FROM Pacchetti p WHERE p.trasportiSingoli2= :trasp").setParameter("trasp", trasp).getResultList().isEmpty()){
				return true;
			}
		}
		return false;
	}

	/**
	 * Ritorna tutte le {@link Disponibilita} relative a un Alloggio
	 * 
	 * @param all = l'{@link AlloggioDTO} di cui caricare le Disponibilita
	 * 
	 */
	@Override
	public ArrayList<DisponibilitaDTO> getDisponibilitaForAlloggio(AlloggioDTO all){
		ArrayList<DisponibilitaDTO> resultado = new ArrayList<DisponibilitaDTO>();
		List<Disponibilita> ld;
		Iterator<Disponibilita> itdisp;

		ld=em.createQuery("SELECT d FROM Disponibilita d WHERE d.alloggi= :idall").setParameter("idall", DTO2Entities.convertAllogioDTO(all)).getResultList();
		itdisp=ld.iterator();

		while(itdisp.hasNext()){
			resultado.add(Entities2DTO.convertDisponibilita(itdisp.next()));
		}

		return resultado;
	}


}
